﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using  XYZCompiler;
using Antlr.Runtime.Tree;

namespace XYZCompiler
{
    class SemanticPart:CompilePartBase
    {
        public override string CompilePartName
        {
            get { return "Semantic Part"; }
        }
        public override CompilePartResult Excute(object param)
        {
            var root = param as BaseTree;
            var xyzMethodNode = new Dictionary<XYZMethod, ITree>();
            var typeTable = new Dictionary<string,XYZType>();
            typeTable.Add("array",new XYZType(null,"array"));
            typeTable.Add("literal",new XYZType(null,"literal"));
            typeTable.Add("bool",new XYZType(null,"bool"));
            typeTable.Add("int", new XYZType(typeTable["literal"],"int"));
            typeTable.Add("long", new XYZType(typeTable["literal"], "long"));
            typeTable.Add("LONGARRAY", new XYZType(typeTable["array"],"LONGARRAY"));
            typeTable.Add("INTARRAY", new XYZType(typeTable["array"],"INTARRAY"));
            ScanType(root, typeTable);
            var methods =  ScanTypeMember(root, typeTable);
            if(methods == null)
                return  new CompilePartResult(FlowController.CompileState.ERROR,null);
            foreach (var method in methods)
            {
                VisteStatment(method.Value,method.Key,typeTable);
            }
            return new CompilePartResult(FlowController.CompileState.SEMANTIC_COMPELETED, typeTable);
        }
        bool ScanType(BaseTree root,Dictionary<string,XYZType> table)
        {
            for (int i = 0; i < root.ChildCount; i++)  //first type scan add all types into type table
            {
                var node = root.GetChild(i).GetChild(0);
                if (table.ContainsKey(node.Text))
                {
                    NotifyError("type already declared:"+node.Text,node.CharPositionInLine,node.Line);
                    continue;
                }
                table.Add(node.Text, new XYZType(null,node.Text));
                table[node.Text].row = node.Line;
                table[node.Text].column = node.CharPositionInLine;
            }
            for (int i = 0; i < root.ChildCount; i++) //add inherit relation
            {
                if (root.GetChild(i).ChildCount > 1 && root.GetChild(i).GetChild(1).Text == "extends")
                {
                    string parentName = root.GetChild(i).GetChild(1).GetChild(0).Text;
                    if (table.ContainsKey(parentName))
                        table[root.GetChild(i).GetChild(0).Text].parent = table[parentName];
                    else
                    {
                        NotifyError("Parent Class Not Declare", root.GetChild(i).GetChild(0).CharPositionInLine, root.GetChild(i).GetChild(0).Line);
                        continue;
                    }
                    //System.Windows.Forms.MessageBox.Show(parentName);
                }
            }
            return true;
        }
        Dictionary<XYZMethod, ITree>  ScanTypeMember(ITree root, Dictionary<string, XYZType> table)
        {
            var methodMap = new Dictionary<XYZMethod, ITree>();
            for (int i = 0; i < root.ChildCount; i++)
            {
                var classNode = root.GetChild(i);
                XYZType curType = table[classNode.GetChild(0).Text];
                for (int j = 0; j < classNode.ChildCount; j++)
                {
                    var memberNode = classNode.GetChild(j);
                    if (memberNode.Text == "VARDECL") //scan class variable member
                    {
                        if(!table.ContainsKey(memberNode.GetChild(0).Text))  //check if type is right
                        {
                            NotifyError("not declare type:"+memberNode.GetChild(0).Text,memberNode.CharPositionInLine,memberNode.Line);
                            continue;
                        }
                        if (curType._varTable.ContainsKey(memberNode.GetChild(1).Text))//check if redeclare
                        {
                            NotifyError("already declare variable:" + memberNode.GetChild(1).Text, memberNode.GetChild(1).CharPositionInLine, memberNode.GetChild(1).Line);
                            continue;
                        }
                        curType._varTable.Add(memberNode.GetChild(1).Text, table[memberNode.GetChild(0).Text]);
                    }
                    else if (memberNode.Text == "METHODDECL" || memberNode.Text =="MAINMETHOD") //scan class method
                    {
                        XYZType rt = null;
                        if (memberNode.Text == "MAINMETHOD")
                            rt = XYZType.NULLType;
                        else if (!table.ContainsKey(memberNode.GetChild(0).Text))
                        {
                            NotifyError("return type not declared!", memberNode.GetChild(0).CharPositionInLine, memberNode.GetChild(0).Line);
                            continue;
                        }
                        else
                            rt = table[memberNode.GetChild(0).Text];
                        var m = new XYZMethod(curType, memberNode.GetChild(1).Text, rt);
                        var ps = memberNode.GetChild(2);
                        bool end = false;
                        for(int x =0;x<ps.ChildCount;x++)
                        {
                            
                            end = false;
                            var p = ps.GetChild(x);
                            if (!table.ContainsKey(p.GetChild(0).Text))
                            {
                                NotifyError("params type not declared :" + p.GetChild(0).Text, p.CharPositionInLine, p.Line);
                                end = true;
                                break;
                            }
                            if(m._params.ContainsKey(p.GetChild(1).Text))
                            {
                                NotifyError("params already declared :" + p.GetChild(1).Text, p.CharPositionInLine, p.Line);
                                end = true;
                                break;
                            }
                            m._params.Add(p.GetChild(1).Text, table[p.GetChild(0).Text]);
                            m._paramsList.Add(m._params[p.GetChild(1).Text]);
                            
                        }
                        if (end)
                            continue;
                        m.row = memberNode.Line;
                        System.Diagnostics.Debug.Assert(m!=null);
                        methodMap.Add(m, memberNode.GetChild(3));
                        if(curType.PickMethod(m.Name,m._paramsList)!=null)
                        {
                            NotifyError("Method signatue is same!",memberNode.CharPositionInLine,memberNode.Line);
                            continue;
                        }
                        curType._methodTable.Add(m);
                    }
                }
            }
            return methodMap;
        }
        void VisteStatment(ITree node, XYZMethod method, Dictionary<string, XYZType> table)
        {
            var tree = node;
            if (tree.Text == "STATEMENT")
                tree = node.GetChild(0);
            //CompilerHelper.Debug(tree.Text);
            switch (tree.Text)
            {
                case "VARDECL":
                    #region
                    var leftType = VistorDecl(tree, method, table);
                    XYZType right1 = null;
                    if (leftType != null && tree.ChildCount > 2)
                    {
                        var initNode = tree.GetChild(2).GetChild(0);
                        if (initNode.Text == "ARRAYINIT")
                        {
                            for (int i = 0; i < initNode.ChildCount; i++)
                            {
                                if (!VisteExpr(initNode.GetChild(i), method, table).IsType(table["literal"]))
                                {
                                    NotifyError(
                                        "array initialization num is not literal",
                                        initNode.GetChild(i).CharPositionInLine,
                                        initNode.GetChild(i).Line);
                                }
                            }
                        }
                        else
                        {
                            right1 = VisteExpr(initNode, method, table);
                            if (!right1.IsType(leftType))
                            {
                                NotifyError("right type is not fit for left type:" + right1.Name,
                                            initNode.CharPositionInLine, initNode.Line);
                            }
                        }
                    }
                    break;
                    #endregion
                case "<=":
                    #region

                    var assignNode = tree;
                    var left = method.GetField(assignNode.GetChild(0).Text);
                    if (left == null)
                    {
                        NotifyError(assignNode.GetChild(0).Text + " not declared", assignNode.CharPositionInLine, assignNode.Line);
                        break;
                    }
                    if (assignNode.ChildCount == 2)
                    {
                        var right = VisteExpr(assignNode.GetChild(1), method, table);
                        if (!right.IsType(left))
                            NotifyError("left right type error can not assign:"+right.Name, assignNode.CharPositionInLine, assignNode.Line);
                    }
                    else
                    {
                        if (!left.IsType(table["array"]))
                        {
                            NotifyError("left is not array type:"+left.Name, assignNode.CharPositionInLine, assignNode.Line);
                            break;
                        }
                        var selector = VisteExpr(assignNode.GetChild(1), method, table);
                        if (!selector.IsType(table["literal"]))
                        {
                            NotifyError("array num  error:"+selector.Name, assignNode.CharPositionInLine, assignNode.Line);
                            break;
                        }
                        var right = VisteExpr(assignNode.GetChild(2), method, table);
                        if (!right.IsType(table["literal"]))
                        {
                            NotifyError("right type error:"+right.Name, assignNode.CharPositionInLine, assignNode.Line);
                            break;
                        }

                    }
                    break;
                    #endregion
                case "if":
                    if (!VisteExpr(tree.GetChild(0), method, table).IsType(table["bool"]))
                        NotifyError("if",0,0);
                    VisteStatment(tree.GetChild(1),method,table);
                    VisteStatment(tree.GetChild(2), method, table);
                    break;
                case "while":
                    if (!VisteExpr(tree.GetChild(0), method, table).IsType(table["bool"]))
                        NotifyError("while", 0, 0);
                    VisteStatment(tree.GetChild(1), method, table);
                    break;
                case "BLOCK":
                    var preDic = new Dictionary<string, XYZType>(method._localVars);
                    ITree postDecl = null;
                    for (int i = 0; i < tree.ChildCount; i++)
                    {
                        if(tree.GetChild(i).Text == "POSTDECL")
                        {
                            postDecl = tree.GetChild(i);
                            continue; 
                        }
                        if(tree.GetChild(i).GetChild(0).Text == "POSTDECL")
                        {
                            postDecl = tree.GetChild(i);
                            continue;
                        }
                        VisteStatment(tree.GetChild(i), method, table);
                    }
                    if(postDecl!=null)
                        VisteStatment(postDecl,method,table);
                    method._localVars = preDic;
                    //pop variable
                    break;
                case "PREDECL":
                    if(!VisteExpr(tree.GetChild(0), method, table).IsType(table["bool"]))
                        NotifyError("predecl exp return type error",tree.CharPositionInLine,tree.Line);
                    break;
                case "POSTDECL":
                    if(!VisteExpr(tree.GetChild(0), method, table).IsType(table["bool"]))
                        NotifyError("predecl exp return type error",tree.CharPositionInLine,tree.Line);
                    break;
                case "return":
                    if(!VisteExpr(tree.GetChild(0), method, table).IsType(method.ReturnType))
                        NotifyError("return type error",tree.CharPositionInLine,tree.Line);
                    break;
                default:
                    CompilerHelper.Debug("1"+tree.Text);
                    break;
            }
            // pop variable;
        }
        XYZType VistorDecl(ITree decl, XYZMethod method, Dictionary<string,XYZType> table)
        {
            string className = decl.GetChild(0).Text;
            if (!table.ContainsKey(className))
            {
                NotifyError("not declare type:"+className,decl.CharPositionInLine,decl.Line);
                return null;                
            }

            string varName = decl.GetChild(1).Text;
            if (!method.VeirfyField(varName))
            {
                NotifyError("already declare variable:"+varName,decl.CharPositionInLine,decl.Line);
                return null;
            }
            method._localVars.Add(varName,table[className]);
            method._variableInfo.Add(varName,new VariableInfo(decl.CharPositionInLine,decl.Line,table[className],varName));
            return table[className];

        }
        XYZType VisteExpr(ITree Exper, XYZMethod method, Dictionary<string, XYZType> table)
        {
            switch (Exper.Text)
            {
                case "||":
                case "&&":
                    if(!VisteExpr(Exper.GetChild(0),method,table).IsType(table["bool"]))
                        NotifyError("logic op left not bool type",Exper.CharPositionInLine,Exper.Line);
                    if (!VisteExpr(Exper.GetChild(1), method, table).IsType(table["bool"]))
                        NotifyError("logic op right bool type", Exper.CharPositionInLine, Exper.Line);
                    return table["bool"];
                    break;
                case "+":
                case "-":
                case "*":
                case "/":
                    if(!VisteExpr(Exper.GetChild(0),method,table).IsType(table["literal"]))
                        NotifyError("binop left not literal type",Exper.CharPositionInLine,Exper.Line);
                    if (!VisteExpr(Exper.GetChild(1), method, table).IsType(table["literal"]))
                        NotifyError("binop right not literal type", Exper.CharPositionInLine, Exper.Line);
                    return table["literal"];
                case ">":
                case "<":
                    if(!VisteExpr(Exper.GetChild(0),method,table).IsType(table["literal"]))
                        NotifyError("cmp left not literal type",Exper.CharPositionInLine,Exper.Line);
                    if (!VisteExpr(Exper.GetChild(1), method, table).IsType(table["literal"]))
                        NotifyError("cmp right not literal type", Exper.CharPositionInLine, Exper.Line);
                    return table["bool"];
                    break;
                case "BOOLTYPE":
                    return table["bool"];
                    break;
                case "LITERALTYPE":
                    return table["literal"];
                    break;
                case "!":
                    if(!VisteExpr(Exper.GetChild(0),method,table).IsType(table["bool"]))
                        NotifyError("! right is not bool type",Exper.CharPositionInLine,Exper.Line);
                    return table["bool"];
                case "new":
                    if (Exper.GetChild(0).Text == "int")
                    {
                        if(!VisteExpr(Exper.GetChild(1),method,table).IsType(table["literal"]))
                            NotifyError("array size not literal type", Exper.CharPositionInLine, Exper.Line);
                        return table["INTARRAY"];
                    }
                    else if (Exper.GetChild(0).Text == "long")
                    {
                        if (!VisteExpr(Exper.GetChild(1), method, table).IsType(table["literal"]))
                            NotifyError("array size not literal type", Exper.CharPositionInLine, Exper.Line);
                        return table["LONGARRAY"];
                    }
                    else
                    {
                        if (table.ContainsKey(Exper.GetChild(0).Text))
                            return table[Exper.GetChild(0).Text];
                        NotifyError("new type not declared:"+Exper.GetChild(0).Text,Exper.CharPositionInLine,Exper.Line);
                        return XYZType.NULLType;
                    }
                    break;
                case "this":
                    return method.owner;
                    break;
                case "length":
                    if (!VisteExpr(Exper.GetChild(0), method, table).IsType(table["array"]))
                        NotifyError("not array type", Exper.CharPositionInLine, Exper.Line);
                    return table["literal"];
                    break;
                case "(":
                    var left = VisteExpr(Exper.GetChild(0), method, table);
                    if (left == XYZType.NULLType)
                    {
                        NotifyError("Var not declared", Exper.CharPositionInLine, Exper.Line);
                        break;
                    }
                    var ps = new List<XYZType>();
                    for (int i = 2; i < Exper.ChildCount;i++ )
                    {
                        ps.Add(VisteExpr(Exper.GetChild(i),method,table));
                    }
                    var m = left.PickMethod(Exper.GetChild(1).Text, ps);
                    if (m != null)
                        return m.ReturnType;
                    break;
                case "VARACCESS":
                    var a =  method.GetField(Exper.GetChild(0).Text);
                    if(a==XYZType.NULLType)
                        NotifyError("var not declared:"+Exper.GetChild(0).Text,Exper.CharPositionInLine,Exper.Line);
                    return a;
                case "[":
                    var array = VisteExpr(Exper.GetChild(0), method, table);
                    if(!array.IsType(table["array"]))
                    {
                        NotifyError("var is not array type:"+array.Name, Exper.CharPositionInLine, Exper.Line);
                    }
                    var arraynum = VisteExpr(Exper.GetChild(1), method, table);
                    if(!arraynum.IsType(table["literal"]))
                    {
                        NotifyError("array selector is not literal type:" + arraynum.Name, Exper.CharPositionInLine, Exper.Line);
                    }
                    break;
                default:
                    CompilerHelper.Debug(Exper.Text);
                    break;

            }
            return XYZType.NULLType;
        }
        protected void ErrorHandler(string s, int c, int r)
        {
            _hasError = true;
            CompilerHelper.Debug(string.Format("{0} in Row:{1} Column:{2}", s, r, c));
            NotifyError(s, c, r);
        }
    }
}
